﻿using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using Math = winS.Unity.Math;

namespace winS.UnityEditor.UIElement
{
    /// <summary>
    /// 支持可拖拽的列表视图
    /// </summary>
    public class ListView<T> : EditorControl
    {
        public static readonly float defaultItemMinHeight = -1f;
        public static readonly float defaultSplitlineHeight = 1f;
        public static readonly float defaultMoveDuration = 0.24f;

        public static readonly Color defaultNormalColor = new Color32(60, 60, 60, 255);
        public static readonly Color defaultHighlightColor = new Color32(44, 93, 135, 255);
        public static readonly Color defaultStripeItemColor = ColorLevelUtility.GetColor(7);

        public event Action<T> onItemAdded;
        public event Action<T> onItemRemoved;
        public event Action<int> onSelectIndexChanged;
        public event Action<int, int> onItemSwapped;

        /// <summary>
        /// 当前选择项
        /// </summary>
        public T selectItem => data[selectIndex];
        /// <summary>
        /// 当前选择项的索引
        /// </summary>
        public int selectIndex { get => _selectIndex; set => SetSelectIndex(value); }


        /// <summary>
        /// 列表项是否可拖拽
        /// </summary>
        public bool draggable { get; private set; } = true;
        /// <summary>
        /// 元素是否可删除.
        /// 如果为 true, 则元素自带一个删除按钮
        /// </summary>
        public bool removable { get; private set; } = true;
        /// <summary>
        /// 是否启用小箭头上下移动控制
        /// </summary>
        public bool keyboardMoveEnabled { get; private set; } = false;
        /// <summary>
        /// 列表项是否启用了条纹样式
        /// </summary>
        public bool stripeItemStyleEnabled { get; private set; } = false;
        /// <summary>
        /// 当视图失去焦点时，是否依旧保留高亮状态.
        /// </summary>
        public bool focusLossHighlightRetention { get; set; } = false;

        /// <summary>
        /// 每个项内容的最小高度.
        /// 默认为Auto
        /// </summary>
        public float itemMinHeight { get; private set; } = defaultItemMinHeight;

        public Color normalColor { get; set; } = defaultNormalColor;
        public Color highlightColor { get; set; } = defaultHighlightColor;
        public Color stripeItemColor { get; set; } = defaultStripeItemColor;

        public IList<T> data { get; private set; }
        public IEnumerable<ListViewItem> items => listViewItemList;
        public Func<T, int, VisualElement> itemDrawFunction { get; private set; }

        private bool dragging;
        private int _selectIndex = -1;

        private readonly List<float> itemHeightList = new List<float>();
        private readonly List<ListViewItem> listViewItemList = new List<ListViewItem>();

        internal ListView(ElementFactory elementFactory) : base(elementFactory)
        {
            focusable = true;
            style.overflow = Overflow.Hidden;
            RegisterCallback<BlurEvent>(BlurCallback);
            RegisterCallback<WheelEvent>(WheelCallback);
            RegisterCallback<MouseUpEvent>(MouseUpCallback);
            RegisterCallback<MouseDownEvent>(MouseDownCallback);
            RegisterCallback<MouseMoveEvent>(MouseMoveCallback);
            style.SetBorderTop(1f, ColorLevelUtility.GetColor(4));
        }

        public void Up()
        {
            if (data == null || data.Count == 0) return;
            if (selectIndex != -1) listViewItemList[selectIndex].ExitHighlightState();
            selectIndex = Math.Max(0, selectIndex - 1);
            listViewItemList[selectIndex].EnterHighlightState();
            onSelectIndexChanged?.Invoke(selectIndex);
        }
        public void Down()
        {
            if (data == null || data.Count == 0) return;
            if (selectIndex != -1) listViewItemList[selectIndex].ExitHighlightState();
            selectIndex = Math.Min(listViewItemList.Count - 1, selectIndex + 1);
            listViewItemList[selectIndex].EnterHighlightState();
            onSelectIndexChanged?.Invoke(selectIndex);
        }
        public void AddItem(T item)
        {
            if (data == null) throw new Exception($"{nameof(ListView)}.{nameof(AddItem)}: {nameof(data)} 不能为 null");
            data.Add(item);

            if (itemDrawFunction != null) CreateAndAddListViewItem(item, data.Count - 1);

            onItemAdded?.Invoke(item);
        }
        public void SetData(IList<T> data, bool refresh = true)
        {
            this.data = data;
            if (refresh) Refresh();
        }
        public void Refresh()
        {
            listViewItemList.Clear();
            Clear();
            if (itemDrawFunction != null && data != null)
            {
                if (listViewItemList.Capacity < data.Count) listViewItemList.Capacity = itemHeightList.Capacity = data.Count;
                for (int i = 0; i < data.Count; i++) CreateAndAddListViewItem(data[i], i);
            }
            if (selectIndex != -1)
            {
                _selectIndex = -1;
                onSelectIndexChanged?.Invoke(-1);
            }
        }
        public void ClearItem()
        {
            data.Clear();
            Refresh();
        }
        public bool RemoveItem(T item)
        {
            int index = data.IndexOf(item);
            if (index == -1) return false;
            RemoveItemAt(index);
            return true;
        }
        public ListViewItem GetItemElement(int index)
        {
            return listViewItemList[index];
        }
        public void RemoveItemAt(int index)
        {
            if (_selectIndex == index) _selectIndex = -1;
            else if (_selectIndex > index) _selectIndex -= 1;
            T item = data[index];
            data.RemoveAt(index);
            ListViewItem removeItem = listViewItemList[index];
            listViewItemList.RemoveAt(index);
            Remove(removeItem);

            for (int i = index; i < data.Count; i++)
            {
                listViewItemList[i].Update(itemDrawFunction(data[i], i), i);
                if (stripeItemStyleEnabled) listViewItemList[i].normalColor = (i & 1) != 0 ? stripeItemColor : normalColor;
            }
            onSelectIndexChanged?.Invoke(_selectIndex);
            onItemRemoved?.Invoke(item);
        }

        public void SetDraggable(bool draggable, bool refreshItem = true)
        {
            if (this.draggable == draggable) return;
            this.draggable = draggable;
            if (refreshItem) listViewItemList.ForEach(item => item.draggable = draggable);
        }
        public void SetRemovable(bool removable, bool refreshItem = true)
        {
            if (this.removable == removable) return;
            this.removable = removable;
            if (refreshItem) listViewItemList.ForEach(item => item.removable = removable);
        }
        /// <summary>
        /// 设置列表项是否启用条纹样式
        /// </summary>
        public void SetStripeItemStyle(bool enableStripeItemStyle, bool refreshItem = true)
        {
            if (stripeItemStyleEnabled == enableStripeItemStyle) return;
            stripeItemStyleEnabled = enableStripeItemStyle;
            if (!refreshItem) return;
            if (!enableStripeItemStyle)
            {
                foreach (var listViewItem in listViewItemList) listViewItem.normalColor = normalColor;
                return;
            }
            bool gray = true;
            foreach (var listViewItem in listViewItemList) listViewItem.normalColor = (gray = !gray) ? stripeItemColor : normalColor;
        }
        public void SetKeyboardMoveEnabled(bool enable)
        {
            if (keyboardMoveEnabled == enable) return;
            keyboardMoveEnabled = enable;
            if (enable) RegisterCallback<KeyDownEvent>(KeyDownCallback);
            else UnregisterCallback<KeyDownEvent>(KeyDownCallback);
        }
        /// <summary>
        /// 设置列表项的最小高度.
        /// 如果 <paramref name="itemMinHeight"/> 小于0, 则表示 <see cref="StyleKeyword.Auto"/>
        /// </summary>
        public void SetItemMinHeight(float itemMinHeight, bool refreshItem = true)
        {
            this.itemMinHeight = itemMinHeight;
            if (!refreshItem) return;
            if (itemMinHeight < 0f) foreach (var listViewItem in listViewItemList) listViewItem.style.minHeight = StyleKeyword.Auto;
            else foreach (var listViewItem in listViewItemList) listViewItem.style.SetMinHeight(itemMinHeight + defaultSplitlineHeight);
        }
        public void SetItemDrawFunction(Func<T, int, VisualElement> itemDrawFunction, bool refresh = true)
        {
            if (itemDrawFunction == null) return;
            bool isFirst = this.itemDrawFunction == null;
            this.itemDrawFunction = itemDrawFunction;
            if (refresh)
            {
                if (isFirst && data != null) for (int i = 0; i < data.Count; i++) CreateAndAddListViewItem(data[i], i);
                else for (int i = 0; i < listViewItemList.Count; i++) listViewItemList[i].Update(itemDrawFunction(data[i], i), i);
            }
        }

        private void SetSelectIndex(int newIndex)
        {
            if (data == null) throw new Exception($"{GetType().Name}.{nameof(SetSelectIndex)}: 设置索引失败, {nameof(data)}不能为null");
            if (newIndex > data.Count - 1) throw new Exception($"{GetType().Name}.{nameof(SetSelectIndex)}: 设置索引失败, 索引超出了范围");
            newIndex = Math.Max(-1, newIndex);
            if (_selectIndex == newIndex) return;
            if (_selectIndex != -1) listViewItemList[_selectIndex].ExitHighlightState();
            _selectIndex = newIndex;
            if (_selectIndex >= 0) listViewItemList[_selectIndex].EnterHighlightState();
            onSelectIndexChanged?.Invoke(_selectIndex);
        }
        private void CreateAndAddListViewItem(T item, int index)
        {
            ListViewItem listViewItem = new ListViewItem(this, index);
            listViewItem.highlightColor = highlightColor;
            listViewItem.normalColor = stripeItemStyleEnabled && (listViewItemList.Count & 1) != 0 ? stripeItemColor : normalColor;
            listViewItem.style.minHeight = itemMinHeight < 0f ? StyleKeyword.Auto : itemMinHeight + defaultSplitlineHeight;
            listViewItem.Add(itemDrawFunction(item, index));
            listViewItemList.Add(listViewItem);
            Add(listViewItem);
        }

        private void BlurCallback(BlurEvent blurEvent)
        {
            if (selectIndex == -1 || keyboardMoveEnabled || focusLossHighlightRetention) return;
            listViewItemList[selectIndex].ExitHighlightState();
        }
        private void WheelCallback(WheelEvent wheelEvent)
        {
            if (!dragging) return;
            wheelEvent.StopImmediatePropagation();
        }
        private void KeyDownCallback(KeyDownEvent keyDownEvent)
        {
            switch (keyDownEvent.keyCode)
            {
                case KeyCode.UpArrow: Up(); break;
                case KeyCode.DownArrow: Down(); break;
                default: return;
            }
            keyDownEvent.StopPropagation();
        }
        private void MouseUpCallback(MouseUpEvent mouseUpEvent)
        {
            if (!dragging) return;
            Insert(selectIndex, listViewItemList[selectIndex]);
            for (int i = selectIndex; i < listViewItemList.Count; i++) listViewItemList[i].ResetOffset();
            dragging = false;
            this.ReleaseMouse();
        }
        private void MouseDownCallback(MouseDownEvent mouseDownEvent)
        {
            if (mouseDownEvent.button != 0) goto Return;
            itemHeightList.Clear();
            float y = mouseDownEvent.localMousePosition.y;
            float currentHeight = 0f;
            float selectedOffsetHeight = 0f;
            float unselectedOffsetHeight = 0f;

            int selectedIndex = selectIndex;
            bool selected = false;
            ListViewItem selectItem = null;
            for (int i = 0; i < listViewItemList.Count; i++)
            {
                ListViewItem listViewItem = listViewItemList[i];
                float itemHeight = listViewItem.height;
                itemHeightList.Add(itemHeight);
                currentHeight += itemHeight;

                if (selected)
                {
                    listViewItem.SetOffset(unselectedOffsetHeight);
                    selectedOffsetHeight += itemHeight;
                }
                else if (y < currentHeight)
                {
                    selected = true;
                    if (selectIndex != -1) listViewItemList[selectIndex].ExitHighlightState();
                    _selectIndex = i;
                    selectItem = listViewItem;
                    unselectedOffsetHeight = itemHeight;
                    selectItem.EnterHighlightState();
                    selectItem.BringToFront();
                    dragging = true;
                    this.CaptureMouse();
                    continue;
                }
                listViewItem.StartDrag();
            }
            if (selected)
            {
                selectItem.SetOffset(-selectedOffsetHeight);
                selectItem.StartDrag();
                if (selectedIndex != selectIndex) onSelectIndexChanged?.Invoke(selectIndex);
            }
        Return:
            mouseDownEvent.StopImmediatePropagation();
        }
        private void MouseMoveCallback(MouseMoveEvent mouseMoveEvent)
        {
            if (!dragging || !draggable) return;
            float localY = mouseMoveEvent.localMousePosition.y;
            if (localY <= 0 || localY >= layout.height) return;

            int swapTargetIndex = GetItemIndexByY(localY);
            if (swapTargetIndex == -1 || swapTargetIndex == selectIndex) goto Return;

            ListViewItem sourceItem = listViewItemList[selectIndex];
            ListViewItem targetItem = listViewItemList[swapTargetIndex];

            float targetItemY = targetItem.y;
            if (localY < targetItemY || localY > targetItemY + sourceItem.height) goto Return;

            if (selectIndex > swapTargetIndex)//上移
            {
                sourceItem.Up(targetItem);
                targetItem.Down(sourceItem);
            }
            else //下移
            {
                targetItem.Up(sourceItem);
                sourceItem.Down(targetItem);
            }

            listViewItemList[selectIndex] = targetItem;
            listViewItemList[swapTargetIndex] = sourceItem;

            //交换高度信息
            (itemHeightList[swapTargetIndex], itemHeightList[selectIndex]) = (itemHeightList[selectIndex], itemHeightList[swapTargetIndex]);

            //交换数据
            T sourceData = data[selectIndex];
            T targetData = data[swapTargetIndex];
            data[selectIndex] = targetData;
            data[swapTargetIndex] = sourceData;
            targetItem.Update(itemDrawFunction(targetData, selectIndex), selectIndex);
            sourceItem.Update(itemDrawFunction(sourceData, swapTargetIndex), swapTargetIndex);
            if (stripeItemStyleEnabled)
            {
                targetItem.normalColor = (selectIndex & 1) != 0 ? stripeItemColor : normalColor;
                sourceItem.normalColor = (swapTargetIndex & 1) != 0 ? stripeItemColor : normalColor;
            }

            onItemSwapped?.Invoke(selectIndex, swapTargetIndex);
            selectIndex = swapTargetIndex;
        Return:
            mouseMoveEvent.StopImmediatePropagation();

            int GetItemIndexByY(float y)//winSTODO: 考虑使用二分替换遍历
            {
                float currentHeight = 0f;
                for (int i = 0; i < itemHeightList.Count; i++)
                {
                    currentHeight += itemHeightList[i];
                    if (y < currentHeight) return i;
                }
                return -1;
            }
        }

        /// <summary>
        /// 列表项
        /// </summary>
        public class ListViewItem : VisualElement
        {
            private readonly static List<TimeValue> transitionDurationList = new List<TimeValue>() { defaultMoveDuration };

            public bool highlighting { get; private set; }
            public bool draggable { get => _draggable; set => SetDragState(value); }
            public bool removable { get => _removable; set => SetRemoveState(value); }
            public float height => layout.height;
            public float topValue => style.top.value.value;
            public float y { get; private set; }

            public Color normalColor { get => _normalColor; set => SetNormalColor(value); }
            public Color highlightColor { get => _highlightColor; set => SetHighlightColor(value); }

            /// <summary>
            /// 表示项里的内容
            /// </summary>
            public VisualElement content { get; private set; }

            public override VisualElement contentContainer => content;

            private int index;
            private bool _draggable;
            private bool _removable;
            private float currentTopOffset;

            private Color _normalColor;
            private Color _highlightColor;

            private Icon dragIcon;
            private Button removeButton;

            private readonly Action<int> removeCallback;

            internal ListViewItem(ListView<T> listView, int index)
            {
                style.flexShrink = 0f;
                style.flexDirection = FlexDirection.Row;
                style.transitionDuration = transitionDurationList;

                hierarchy.Add(content = EditorElementUtility.elementFactory.CreateColumn(1f));
                style.SetBorderColor(listView.elementFactory.borderColor);
                style.borderBottomWidth = style.borderLeftWidth = style.borderRightWidth = defaultSplitlineHeight;

                this.index = index;
                removeCallback = listView.RemoveItemAt;

                if (_draggable = listView.draggable) CreateDragIcon();
                if (_removable = listView.removable) CreateRemoveButton();
            }

            public void ExitHighlightState()
            {
                highlighting = false;
                style.SetBackgroundColor(normalColor);
            }
            public void EnterHighlightState()
            {
                highlighting = true;
                style.SetBackgroundColor(highlightColor);
            }
            public void SetOffset(float topOffset)
            {
                DisableTransition();
                currentTopOffset += topOffset;
                UpdateTop();
                EnableTransition();
            }
            public void StartDrag()
            {
                y = layout.y;
            }
            public void ResetOffset()
            {
                DisableTransition();
                currentTopOffset = 0f;
                UpdateTop();
                EnableTransition();
            }
            public void EnableTransition()
            {
                style.transitionProperty = EditorElementUtility.topPropertyList;
            }
            public void DisableTransition()
            {
                style.transitionProperty = EditorElementUtility.emptyPropertyList;
            }
            public void SetDragState(bool enable)
            {
                if (_draggable == enable) return;
                _draggable = enable;
                if (enable)
                {
                    if (dragIcon == null) CreateDragIcon();
                    else dragIcon.style.display = DisplayStyle.Flex;
                }
                else
                {
                    if (dragIcon == null) return;
                    else dragIcon.style.display = DisplayStyle.None;
                }
            }
            public void SetRemoveState(bool enable)
            {
                if (_removable == enable) return;
                _removable = enable;
                if (enable)
                {
                    if (removeButton == null) CreateRemoveButton();
                    else removeButton.style.display = DisplayStyle.Flex;
                }
                else
                {
                    if (removeButton == null) return;
                    else removeButton.style.display = DisplayStyle.None;
                }
            }

            public void Up(ListViewItem target)
            {
                currentTopOffset -= target.height;
                UpdateTop();
                y -= target.height;
            }
            public void Down(ListViewItem target)
            {
                currentTopOffset += target.height;
                UpdateTop();
                y += target.height;
            }
            public void Update(VisualElement element, int index)
            {
                this.index = index;
                content.RemoveAt(0);
                content.Add(element);
            }

            private void SetNormalColor(Color color)
            {
                _normalColor = color;
                if (highlighting) return;
                style.SetBackgroundColor(color);
            }
            private void SetHighlightColor(Color color)
            {
                _highlightColor = color;
                if (!highlighting) return;
                style.SetBackgroundColor(color);
            }
            private void UpdateTop()
            {
                style.top = currentTopOffset;
            }
            private void CreateDragIcon()
            {
                hierarchy.Insert(0, dragIcon = EditorElementUtility.elementFactory.CreateIcon(PackageAssets.Icons.drag, 16f));
                dragIcon.style.marginLeft = dragIcon.style.marginRight = 4f;
                dragIcon.style.flexShrink = 0f;
                dragIcon.style.SetCursor(MouseCursor.Pan);
            }
            private void CreateRemoveButton()
            {
                removeButton = EditorElementUtility.elementFactory.CreateButtonWithName("移除按钮", PackageAssets.Icons.delete, 16, () => removeCallback(index));
                removeButton.style.flexShrink = 0f;
                hierarchy.Insert(hierarchy.childCount, removeButton);
            }
        }
    }
}